<!DOCTYPE html>
<html>
<head>
	<title>VDO.Ninja IFRAME API - Interactive Developer Console</title>
	<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no" />
	<meta content="text/html;charset=utf-8" http-equiv="Content-Type" />
	<link rel="stylesheet" href="./iframe.css" />
	<script src="iframe-examples.js"></script>
	<script>
	// Global state management
	const ExpandableCards = {};
	const ActiveConnections = new Map(); // Track active connections by streamID
	const MessageHistory = [];
	const MAX_HISTORY = 100;

	/**
	 * Main function to load and initialize a VDO.Ninja iframe
	 * This demonstrates best practices for iframe integration
	 */
	function loadIframe() {
		var exampleId = generateId();
		
		// Initialize expandable card states
		ExpandableCards[exampleId] = {
			iframe: false,
			companion: false,
		}

		// Process and validate the iframe URL
		var iframesrc = document.getElementById("viewlink").value;
		if (!(iframesrc.startsWith("?") || iframesrc.startsWith("&") || iframesrc.startsWith("http"))) {
			if (iframesrc.split(".").length > 1) {
				iframesrc = "https://" + iframesrc;
			}
		}

		// Create main container elements
		var container = document.getElementById("container");
		var iframeExample = createIframeExample(exampleId, iframesrc);
		container.prepend(iframeExample.container);

		// Set up message listener for this specific iframe
		setupMessageListener(iframeExample.iframe, exampleId);
	}

	/**
	 * Creates the complete iframe example structure
	 */
	function createIframeExample(exampleId, iframesrc) {
		// Create iframe with proper permissions
		var iframe = newElement("iframe", {
			allow: "document-domain;encrypted-media;sync-xhr;usb;web-share;cross-origin-isolated;midi *;geolocation;camera *;microphone *;fullscreen;picture-in-picture;display-capture;accelerometer;autoplay;gyroscope;screen-wake-lock;",
			src: addUrlParams(iframesrc, ["cleanoutput", "transparent", "hidemenu"])
		});
		iframe.setAttribute("allowtransparency", "true");
		iframe.setAttribute("crossorigin", "anonymous");
		iframe.setAttribute("credentialless", "true");

		// Create UI structure
		var container = newElement("div", { id: exampleId, classList: "iframe-example" });
		var header = createExampleHeader(exampleId, iframesrc, container);
		var body = createExampleBody(exampleId, iframe);
		
		container.append(header, body);
		
		return { container, iframe, id: exampleId };
	}

	/**
	 * Creates the header section with URL display and controls
	 */
	function createExampleHeader(exampleId, iframesrc, container) {
		var header = newElement("div", { 
			classList: "example-header", 
			innerHTML: `<span title="${iframesrc}">${iframesrc}</span>` 
		});

		// Add connection status indicator
		var statusIndicator = newElement("span", {
			id: `status-${exampleId}`,
			classList: "status-indicator",
			innerHTML: "⚪ Initializing"
		});

		// Add remove button
		var removeButton = newElement("button", {
			innerHTML: "❌ REMOVE",
			onclick: function() {
				if (confirm("Remove this iframe instance?")) {
					container.parentNode.removeChild(container);
					delete ExpandableCards[exampleId];
				}
			}
		});

		header.append(statusIndicator, removeButton);
		return header;
	}

	/**
	 * Creates the main body section with controls and output
	 */
	function createExampleBody(exampleId, iframe) {
		var body = newElement("div", { classList: "example-body" });
		
		// Left panel - Controls
		var controlsContainer = createControlsPanel(exampleId, iframe);
		
		// Right panel - Output
		var outputContainer = createOutputPanel(exampleId, iframe);
		
		// Bottom panel - Stream data
		var streamDataLogs = newElement("div", { 
			classList: "stream-data-logs",
			id: `stream-data-${exampleId}`
		});

		body.append(controlsContainer, outputContainer, streamDataLogs);
		return body;
	}

	/**
	 * Creates the controls panel with organized API sections
	 */
	function createControlsPanel(exampleId, iframe) {
		var controlsContainer = newElement("div", { classList: "controls" });

		// Add quick access section
		var quickAccess = createQuickAccessSection(exampleId, iframe);
		controlsContainer.appendChild(quickAccess);

		// Add API sections
		var iframeSection = createAPISection(
			IFRAME_API,
			{ 
				id: exampleId,
				guestId: 'iframe',
				title: 'IFRAME API',
				description: 'Commands exclusive to the IFRAME API for direct control',
				iframe: iframe
			}
		);

		var companionSection = createAPISection(
			COMPANION_API,
			{
				id: exampleId,
				guestId: 'companion',
				title: 'HTTP/WSS API',
				description: 'Companion.Ninja API commands available via iframe',
				info: '<a href="https://github.com/steveseguin/Companion-Ninja#api-commands" target="_blank">📚 Full API Documentation</a>',
				iframe: iframe
			}
		);

		// Add guest targeting section
		var guestTargeting = createGuestTargetingSection(exampleId, iframe, controlsContainer);

		controlsContainer.append(
			iframeSection.header,
			iframeSection.content,
			companionSection.header,
			companionSection.content,
			guestTargeting
		);

		return controlsContainer;
	}

	/**
	 * Creates a quick access section for common commands
	 */
	function createQuickAccessSection(exampleId, iframe) {
		var section = newElement("div", { classList: "quick-access" });
		section.innerHTML = `
			<h3>🚀 Quick Actions</h3>
			<div class="quick-buttons">
				<button onclick="postToFrame('${exampleId}', { mic: 'toggle' })">🎤 Toggle Mic</button>
				<button onclick="postToFrame('${exampleId}', { camera: 'toggle' })">📹 Toggle Camera</button>
				<button onclick="postToFrame('${exampleId}', { speaker: 'toggle' })">🔊 Toggle Speaker</button>
				<button onclick="postToFrame('${exampleId}', { getStreamIDs: true })">📋 List Streams</button>
				<button onclick="postToFrame('${exampleId}', { getStats: true })">📊 Get Stats</button>
				<button onclick="postToFrame('${exampleId}', { getDeviceList: true })">🎛️ List Devices</button>
			</div>
		`;
		return section;
	}

	/**
	 * Creates the output panel with iframe and logs
	 */
	function createOutputPanel(exampleId, iframe) {
		var outputContainer = newElement("div", { classList: "output-container" });
		
		// Add iframe
		outputContainer.appendChild(iframe);
		
		// Add tabs for different log types
		var tabsContainer = createLogTabs(exampleId);
		outputContainer.appendChild(tabsContainer);
		
		// Add custom post message input
		var customPost = createCustomPostSection(exampleId, iframe);
		outputContainer.appendChild(customPost);

		return outputContainer;
	}

	/**
	 * Creates tabbed log interface
	 */
	function createLogTabs(exampleId) {
		var tabsContainer = newElement("div", { classList: "tabs-container" });
		
		var tabs = newElement("div", { classList: "tabs" });
		tabs.innerHTML = `
			<button class="tab active" onclick="switchTab('${exampleId}', 'all')">All Logs</button>
			<button class="tab" onclick="switchTab('${exampleId}', 'events')">Events</button>
			<button class="tab" onclick="switchTab('${exampleId}', 'commands')">Commands</button>
			<button class="tab" onclick="switchTab('${exampleId}', 'stats')">Stats</button>
		`;

		var logContainer = newElement("div", { 
			id: `${exampleId}-logs`,
			classList: "main-log active-tab-content",
			dataset: { tab: "all" }
		});

		tabsContainer.append(tabs, logContainer);
		return tabsContainer;
	}

	/**
	 * Creates custom post message section with JSON validation
	 */
	function createCustomPostSection(exampleId, iframe) {
		var container = newElement("div", { classList: "custom-post" });
		
		var input = newElement("textarea", {
			classList: "custom-post-input",
			placeholder: "Enter custom JSON message (e.g., { \"action\": \"value\" })",
			rows: 3
		});

		var helpText = newElement("span", {
			classList: "help-text",
			innerHTML: "💡 Tip: Use Ctrl+Enter to send"
		});

		var sendButton = newElement("button", { 
			innerText: "📤 SEND JSON",
			onclick: () => sendCustomMessage(exampleId, input, iframe)
		});

		// Add keyboard shortcut
		input.onkeydown = function(e) {
			if (e.ctrlKey && e.key === 'Enter') {
				sendCustomMessage(exampleId, input, iframe);
			}
		};

		container.append(input, helpText, sendButton);
		return container;
	}

	/**
	 * Sends custom JSON message with validation
	 */
	function sendCustomMessage(exampleId, input, iframe) {
		try {
			const message = JSON.parse(input.value);
			postToFrame(exampleId, message, iframe);
			input.value = ''; // Clear on success
			input.classList.remove('error');
		} catch (error) {
			input.classList.add('error');
			logOutput(`❌ JSON Error: ${error.message}`, {
				exampleId: exampleId,
				classList: "error-log"
			});
		}
	}

	/**
	 * Creates an API section with collapsible commands
	 */
	function createAPISection(api, config) {
		var sectionId = `${config.id}-${config.guestId}`;
		
		var header = newElement("div", { classList: "api-section-header" });
		var toggleBtn = newElement("button", {
			innerText: "▶ Show",
			onclick: function() {
				toggleSection(config.id, config.guestId, this);
			}
		});

		var headerText = newElement("div");
		headerText.innerHTML = `
			<h3>${config.title}</h3>
			<p><em>${config.description}</em></p>
			${config.info || ''}
		`;

		header.append(headerText, toggleBtn);

		var content = newElement("div", {
			classList: "hidden",
			id: sectionId
		});

		// Group commands by category
		var categorizedCommands = categorizeCommands(api);
		Object.entries(categorizedCommands).forEach(([category, commands]) => {
			var categoryDiv = newElement("div", { classList: "command-category" });
			categoryDiv.innerHTML = `<h4>📁 ${category}</h4>`;
			
			Object.entries(commands).forEach(([name, data]) => {
				var commandDiv = createCommandInterface(name, data, config.iframe, config.id);
				categoryDiv.appendChild(commandDiv);
			});
			
			content.appendChild(categoryDiv);
		});

		return { header, content };
	}

	/**
	 * Categorizes commands for better organization
	 */
	function categorizeCommands(api) {
		const categories = {
			'Audio Controls': ['mic', 'mute', 'speaker', 'volume', 'panning', 'audiobitrate', 'targetAudioBitrate', 'PPT'],
			'Video Controls': ['camera', 'bitrate', 'targetBitrate', 'scale', 'keyframe', 'forceKeyframe'],
			'Recording': ['record'],
			'Communication': ['sendChat', 'sendMessage', 'sendRequest'],
			'System': ['reload', 'close', 'hangup'],
			'Information': ['getStats', 'getStreamIDs', 'getDeviceList', 'getDetailedState', 'getLoudness'],
			'Layout': ['add', 'remove', 'layout', 'style'],
			'Groups': ['group', 'groups', 'groupView'],
			'Other': []
		};

		const categorized = {};
		Object.keys(categories).forEach(cat => categorized[cat] = {});

		Object.entries(api).forEach(([name, data]) => {
			let placed = false;
			for (const [category, commands] of Object.entries(categories)) {
				if (commands.includes(name)) {
					categorized[category][name] = data;
					placed = true;
					break;
				}
			}
			if (!placed) {
				categorized['Other'][name] = data;
			}
		});

		// Remove empty categories
		Object.keys(categorized).forEach(cat => {
			if (Object.keys(categorized[cat]).length === 0) {
				delete categorized[cat];
			}
		});

		return categorized;
	}

	/**
	 * Creates command interface with proper controls
	 */
	function createCommandInterface(name, data, iframe, exampleId) {
		var container = newElement("div", { classList: "command-container" });
		var header = newElement("h5", { innerHTML: name });
		container.appendChild(header);

		if (data.options) {
			data.options.forEach((option, i) => {
				var button = newElement("button", {
					innerHTML: data.labels?.[i] || JSON.stringify(option),
					onclick: () => postToFrame(exampleId, data.result(option), iframe)
				});
				container.appendChild(button);
			});
		}

		if (data.input) {
			var inputWrapper = newElement("div", { classList: "input-wrapper" });
			var input = newElement("input", data.input);
			
			if (input.type === 'range') {
				var valueDisplay = newElement("span", { 
					classList: "range-value",
					innerText: input.value 
				});
				input.oninput = function() {
					valueDisplay.innerText = this.value;
					postToFrame(exampleId, data.result(this.value), iframe);
				};
				inputWrapper.append(input, valueDisplay);
			} else {
				var sendBtn = newElement("button", {
					innerText: "Send",
					onclick: () => postToFrame(exampleId, data.result(input.value), iframe)
				});
				inputWrapper.append(input, sendBtn);
			}
			
			container.appendChild(inputWrapper);
		}

		return container;
	}

	/**
	 * Creates guest targeting section
	 */
	function createGuestTargetingSection(exampleId, iframe, controlsContainer) {
		var section = newElement("div", { classList: "target-guest" });
		section.innerHTML = `
			<h3>🎯 Target Guest Commands</h3>
			<p><em>Directors can target commands to individual guests</em></p>
		`;

		var targetType = newElement("select", {
			id: `${exampleId}-target-type`
		});
		targetType.innerHTML = `
			<option value="slot">By Slot Number</option>
			<option value="streamID">By Stream ID</option>
			<option value="UUID">By UUID</option>
		`;

		var targetValue = newElement("input", {
			id: `${exampleId}-target-value`,
			type: "text",
			placeholder: "Enter target value"
		});

		var generateBtn = newElement("button", {
			innerText: "Generate Guest Commands",
			onclick: () => {
				const type = targetType.value;
				const value = targetValue.value;
				if (!value) {
					alert("Please enter a target value");
					return;
				}
				generateGuestCommands(exampleId, type, value, iframe, controlsContainer);
			}
		});

		section.append(targetType, targetValue, generateBtn);
		return section;
	}

	/**
	 * Generates guest-specific commands
	 */
	function generateGuestCommands(exampleId, targetType, targetValue, iframe, container) {
		const guestId = `guest-${targetType}-${targetValue}`;
		if (document.getElementById(`${exampleId}-${guestId}`)) {
			alert("Commands for this target already generated");
			return;
		}

		const guestAPI = guestTargetedAPI(targetValue);
		const section = createAPISection(guestAPI, {
			id: exampleId,
			guestId: guestId,
			title: `Guest Commands: ${targetType} = ${targetValue}`,
			description: `Target specific commands to ${targetType}: ${targetValue}`,
			iframe: iframe
		});

		container.append(section.header, section.content);
	}

	/**
	 * Sets up message listener for iframe communication
	 */
	function setupMessageListener(iframe, exampleId) {
		// Media handling for frame capture
		const media = {
			tracks: {},
			streams: {}
		};

		// Event listener
		const messageHandler = function(e) {
			if (e.source !== iframe.contentWindow) return;

			// Update status indicator
			updateConnectionStatus(exampleId, "🟢 Connected");

			if (typeof e.data !== "object") {
				console.log(e.data);
				return;
			}

			// Store in history
			MessageHistory.push({
				timestamp: new Date(),
				exampleId: exampleId,
				data: e.data
			});
			if (MessageHistory.length > MAX_HISTORY) {
				MessageHistory.shift();
			}

			// Handle different message types
			handleIncomingMessage(e.data, exampleId, media);
		};

		window.addEventListener("message", messageHandler);
		window.addEventListener("messageerror", e => console.error(e));
	}

	/**
	 * Handles incoming messages from iframe
	 */
	function handleIncomingMessage(data, exampleId, media) {
		// Handle frame capture
		if (data.frame) {
			handleFrameCapture(data, exampleId, media);
			return;
		}

		// Log based on message type
		let logType = "event";
		let formatted = "";

		// Handle specific message types
		if (data.action) {
			handleActionMessage(data, exampleId);
			return;
		}

		if (data.stats) {
			handleStatsMessage(data.stats, exampleId);
			return;
		}

		if (data.streamIDs) {
			handleStreamIDsMessage(data.streamIDs, exampleId);
			return;
		}

		if (data.deviceList) {
			handleDeviceListMessage(data.deviceList, exampleId);
			return;
		}

		if (data.loudness) {
			handleLoudnessMessage(data.loudness, exampleId);
			return;
		}

		if (data.sensors) {
			handleSensorsMessage(data.sensors, exampleId);
			return;
		}

		// Default logging
		console.log(data);
		logOutput(`📥 ${JSON.stringify(data, null, 2)}`, {
			exampleId: exampleId,
			type: "event"
		});
	}

	/**
	 * Handles frame capture for video/audio
	 */
	function handleFrameCapture(data, exampleId, media) {
		if (!media.tracks[data.trackID]) {
			media.tracks[data.trackID] = {};
			media.tracks[data.trackID].generator = new MediaStreamTrackGenerator({kind: data.kind});
			media.tracks[data.trackID].stream = new MediaStream([media.tracks[data.trackID].generator]);
			media.tracks[data.trackID].frameWriter = media.tracks[data.trackID].generator.writable.getWriter();
			
			media.tracks[data.trackID].frameWriter.write(data.frame);
			
			if (!media.streams[data.streamID]) {
				const video = createVideoElement(data.streamID);
				video.srcObject = media.tracks[data.trackID].stream;
				document.getElementById(`${exampleId}-logs`).appendChild(video);
				media.streams[data.streamID] = video;
			} else {
				updateVideoStream(media.streams[data.streamID], media.tracks[data.trackID].stream, data.kind);
			}
		} else {
			media.tracks[data.trackID].frameWriter.write(data.frame);
		}
	}

	/**
	 * Creates video element for captured streams
	 */
	function createVideoElement(streamID) {
		const video = document.createElement("video");
		video.id = `video_${streamID}`;
		video.autoplay = true;
		video.muted = true;
		video.controls = true;
		video.style.maxWidth = "300px";
		return video;
	}

	/**
	 * Updates video stream tracks
	 */
	function updateVideoStream(video, stream, kind) {
		if (kind === "video") {
			video.srcObject.getVideoTracks().forEach(trk => {
				video.srcObject.removeTrack(trk);
			});
		} else if (kind === "audio") {
			video.srcObject.getAudioTracks().forEach(trk => {
				video.srcObject.removeTrack(trk);
			});
		}
		stream.getTracks().forEach(trk => {
			video.srcObject.addTrack(trk);
		});
	}

	/**
	 * Handles action messages
	 */
	function handleActionMessage(data, exampleId) {
		const actionHandlers = {
			"guest-connected": () => {
				ActiveConnections.set(data.streamID, data.value);
				logOutput(`✅ Guest connected: ${data.streamID} (${data.value?.label || 'No label'})`, {
					exampleId: exampleId,
					type: "event",
					classList: "success-log"
				});
			},
			"guest-disconnected": () => {
				ActiveConnections.delete(data.streamID);
				logOutput(`❌ Guest disconnected: ${data.streamID}`, {
					exampleId: exampleId,
					type: "event",
					classList: "error-log"
				});
			},
			"director-connected": () => {
				logOutput(`👑 Director connected`, {
					exampleId: exampleId,
					type: "event",
					classList: "success-log"
				});
			},
			"image-frame-capture": () => {
				const img = document.createElement("img");
				img.src = URL.createObjectURL(data.value.imageData);
				img.style.maxWidth = "260px";
				document.getElementById(`${exampleId}-logs`).appendChild(img);
			}
		};

		const handler = actionHandlers[data.action];
		if (handler) {
			handler();
		} else if (data.action !== "loudness" && data.action !== "view-stats-updated") {
			logOutput(`📌 Action: ${data.action}${data.value ? ` - ${JSON.stringify(data.value)}` : ''}`, {
				exampleId: exampleId,
				type: "event"
			});
		}
	}

	/**
	 * Handles stats messages
	 */
	function handleStatsMessage(stats, exampleId) {
		let formatted = `📊 <strong>Connection Stats</strong><br/>`;
		formatted += `Inbound: ${stats.total_inbound_connections} | Outbound: ${stats.total_outbound_connections}<br/>`;
		
		for (const streamID in stats.inbound) {
			formatted += `<br/><strong>Stream: ${streamID}</strong><br/>`;
			formatted += formatObject(stats.inbound[streamID]);
		}
		
		logOutput(formatted, {
			exampleId: exampleId,
			type: "stats"
		});
	}

	/**
	 * Handles stream IDs message
	 */
	function handleStreamIDsMessage(streamIDs, exampleId) {
		let formatted = `📋 <strong>Active Streams</strong><br/>`;
		for (const [id, label] of Object.entries(streamIDs)) {
			formatted += `• ${id}: ${label || 'No label'}<br/>`;
		}
		logOutput(formatted, {
			exampleId: exampleId,
			type: "event"
		});
	}

	/**
	 * Handles device list message
	 */
	function handleDeviceListMessage(deviceList, exampleId) {
		let formatted = `🎛️ <strong>Available Devices</strong><br/>`;
		const grouped = deviceList.reduce((acc, device) => {
			if (!acc[device.kind]) acc[device.kind] = [];
			acc[device.kind].push(device);
			return acc;
		}, {});

		for (const [kind, devices] of Object.entries(grouped)) {
			formatted += `<br/><strong>${kind}:</strong><br/>`;
			devices.forEach(device => {
				formatted += `• ${device.label || 'Unnamed device'}<br/>`;
			});
		}
		
		logOutput(formatted, {
			exampleId: exampleId,
			type: "event"
		});
	}

	/**
	 * Handles loudness data
	 */
	function handleLoudnessMessage(loudness, exampleId) {
		const container = document.getElementById(`stream-data-${exampleId}`);
		let loudnessDiv = document.getElementById(`${exampleId}-loudness`);
		
		if (!loudnessDiv) {
			loudnessDiv = newElement("div", {
				id: `${exampleId}-loudness`,
				classList: "loudness-display"
			});
			container.appendChild(loudnessDiv);
		}

		let html = `<h4>🔊 Audio Levels</h4>`;
		for (const [streamID, level] of Object.entries(loudness)) {
			const barWidth = Math.min(100, Math.max(0, (level + 40) * 2.5));
			html += `
				<div class="loudness-meter">
					<span>${streamID}:</span>
					<div class="loudness-bar">
						<div class="loudness-fill" style="width: ${barWidth}%"></div>
					</div>
					<span>${level.toFixed(1)} dB</span>
				</div>
			`;
		}
		
		loudnessDiv.innerHTML = html;
	}

	/**
	 * Handles sensor data
	 */
	function handleSensorsMessage(sensors, exampleId) {
		const container = document.getElementById(`stream-data-${exampleId}`);
		let sensorsDiv = document.getElementById(`${exampleId}-sensors`);
		
		if (!sensorsDiv) {
			sensorsDiv = newElement("div", {
				id: `${exampleId}-sensors`,
				classList: "sensors-display"
			});
			container.appendChild(sensorsDiv);
		}

		let html = `<h4>📡 Sensor Data</h4>`;
		const sensorTypes = {
			lin: "Linear",
			acc: "Acceleration",
			gyro: "Gyroscope",
			mag: "Magnetometer"
		};

		for (const [type, label] of Object.entries(sensorTypes)) {
			if (sensors[type]) {
				html += `<strong>${label}:</strong><br/>`;
				for (const [axis, value] of Object.entries(sensors[type])) {
					html += `${axis}: ${(Math.round(value * 100000) / 100000)}<br/>`;
				}
			}
		}
		
		sensorsDiv.innerHTML = html;
	}

	// Helper functions

	/**
	 * Posts a message to the iframe
	 */
	function postToFrame(exampleId, message, iframe) {
		if (!iframe) {
			const container = document.getElementById(exampleId);
			iframe = container?.querySelector('iframe');
		}
		
		if (!iframe) {
			console.error("Iframe not found for example:", exampleId);
			return;
		}

		iframe.contentWindow.postMessage(message, '*');
		logOutput(`📤 Sent: ${JSON.stringify(message)}`, {
			exampleId: exampleId,
			type: "command",
			classList: "post-log"
		});
	}

	// Make function globally accessible
	window.postToFrame = postToFrame;

	/**
	 * Logs output to the appropriate window
	 */
	function logOutput(content, options = {}) {
		const logWindow = document.getElementById(`${options.exampleId}-logs`);
		if (!logWindow) return;

		const entry = newElement("div", {
			classList: options.classList || "log-entry",
			innerHTML: `<span class="timestamp">${new Date().toLocaleTimeString()}</span> ${content}`
		});

		if (options.type) {
			entry.dataset.logType = options.type;
		}

		logWindow.appendChild(entry);
		logWindow.scrollTop = logWindow.scrollHeight;

		// Limit log entries
		while (logWindow.children.length > 100) {
			logWindow.removeChild(logWindow.firstChild);
		}
	}

	/**
	 * Updates connection status indicator
	 */
	function updateConnectionStatus(exampleId, status) {
		const indicator = document.getElementById(`status-${exampleId}`);
		if (indicator) {
			indicator.innerHTML = status;
		}
	}

	/**
	 * Toggles section visibility
	 */
	function toggleSection(exampleId, sectionId, button) {
		ExpandableCards[exampleId][sectionId] = !ExpandableCards[exampleId][sectionId];
		const section = document.getElementById(`${exampleId}-${sectionId}`);
		
		if (ExpandableCards[exampleId][sectionId]) {
			section.classList.remove("hidden");
			button.innerText = "▼ Hide";
		} else {
			section.classList.add("hidden");
			button.innerText = "▶ Show";
		}
	}

	/**
	 * Switches between log tabs
	 */
	function switchTab(exampleId, tabName) {
		const container = document.getElementById(`${exampleId}-logs`).parentElement;
		
		// Update tab buttons
		container.querySelectorAll('.tab').forEach(tab => {
			tab.classList.remove('active');
		});
		event.target.classList.add('active');

		// Filter logs
		const logWindow = document.getElementById(`${exampleId}-logs`);
		logWindow.querySelectorAll('.log-entry').forEach(entry => {
			if (tabName === 'all' || entry.dataset.logType === tabName) {
				entry.style.display = 'block';
			} else {
				entry.style.display = 'none';
			}
		});
	}

	// Make function globally accessible
	window.switchTab = switchTab;

	/**
	 * Formats an object for display
	 */
	function formatObject(obj, indent = "") {
		let formatted = "";
		for (const [key, value] of Object.entries(obj)) {
			if (typeof value === "object" && value !== null) {
				formatted += `${indent}<strong>${key}:</strong><br/>`;
				formatted += formatObject(value, indent + "&nbsp;&nbsp;");
			} else {
				formatted += `${indent}<strong>${key}:</strong> ${value}<br/>`;
			}
		}
		return formatted;
	}

	/**
	 * Helper to create elements with attributes
	 */
	function newElement(type, attributes) {
		const el = document.createElement(type);
		for (const attr in attributes) {
			if (attr === 'dataset') {
				Object.assign(el.dataset, attributes[attr]);
			} else {
				el[attr] = attributes[attr];
			}
		}
		return el;
	}

	/**
	 * Generates a random ID
	 */
	function generateId() {
		return Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 10);
	}

	/**
	 * Adds URL parameters based on checkboxes
	 */
	function addUrlParams(_url, paramCheckboxes) {
		let url = _url || "./";
		
		for (const param of paramCheckboxes) {
			if (document.getElementById(param)?.checked) {
				url += url.includes("?") ? '&' : '?';
				url += param;
			}
		}
		
		return url;
	}

	/**
	 * Global error handler
	 */
	window.addEventListener('error', function(e) {
		console.error('Global error:', e);
	});

	/**
	 * Initialize on page load
	 */
	window.addEventListener('DOMContentLoaded', function() {
		// Add example URL if empty
		const viewlink = document.getElementById('viewlink');
		if (!viewlink.value) {
			viewlink.placeholder = "e.g., https://vdo.ninja/?view=teststream or just 'vdo.ninja/?room=myroom'";
		}

		// Add keyboard shortcuts
		document.addEventListener('keydown', function(e) {
			if (e.ctrlKey && e.key === 'k') {
				e.preventDefault();
				document.querySelector('.custom-post-input')?.focus();
			}
		});
	});
	</script>
</head>
<body>
	<div class="header-section">
		<h1>🚀 VDO.Ninja IFRAME API - Interactive Developer Console</h1>
		<p>Test and explore the VDO.Ninja IFRAME API with this interactive tool. Enter a VDO.Ninja URL below to get started.</p>
	</div>
	
	<div class="url-input-section">
		<input placeholder="Enter a VDO.Ninja View URL (e.g., vdo.ninja/?view=abc123)" id="viewlink" />
		<button onclick="loadIframe();" class="primary-button">➕ ADD IFRAME</button>
		
		<div class="url-options">
			<label><input type="checkbox" id="cleanoutput" checked> Clean Output</label>
			<label><input type="checkbox" id="transparent"> Transparent</label>
			<label><input type="checkbox" id="hidemenu" checked> Hide Menu</label>
		</div>
	</div>
	
	<div class="info-section">
		<details>
			<summary>📖 Quick Start Guide</summary>
			<div class="guide-content">
				<h3>Getting Started:</h3>
				<ol>
					<li>Enter a VDO.Ninja URL in the input field above</li>
					<li>Click "ADD IFRAME" to create a new instance</li>
					<li>Use the control panel to send commands</li>
					<li>Monitor responses in the log window</li>
				</ol>
				
				<h3>Tips:</h3>
				<ul>
					<li>Use <kbd>Ctrl</kbd>+<kbd>K</kbd> to focus the JSON input</li>
					<li>Use <kbd>Ctrl</kbd>+<kbd>Enter</kbd> in JSON input to send</li>
					<li>Click on log entries to copy their content</li>
					<li>Check the browser console for detailed debugging</li>
				</ul>
				
				<h3>Common Use Cases:</h3>
				<ul>
					<li><strong>Testing:</strong> Verify API commands work correctly</li>
					<li><strong>Debugging:</strong> Monitor events and responses</li>
					<li><strong>Development:</strong> Prototype integrations</li>
					<li><strong>Learning:</strong> Explore API capabilities</li>
				</ul>
			</div>
		</details>
		
		<details>
			<summary>🔗 Useful Links</summary>
			<div class="links-content">
				<a href="https://github.com/steveseguin/vdo.ninja" target="_blank">📦 VDO.Ninja GitHub</a>
				<a href="https://docs.vdo.ninja" target="_blank">📚 Documentation</a>
				<a href="https://discord.vdo.ninja" target="_blank">💬 Discord Community</a>
				<a href="https://github.com/steveseguin/Companion-Ninja" target="_blank">🤖 Companion API</a>
			</div>
		</details>
	</div>
	
	<div id="container"></div>
	
	<style>
		/* Additional developer-friendly styles */
		.header-section {
			background: #1a1a2e;
			color: white;
			padding: 20px;
			margin: -10px -10px 20px -10px;
			text-align: center;
		}
		
		.header-section h1 {
			margin: 0 0 10px 0;
			font-weight: 300;
		}
		
		.url-input-section {
			background: white;
			padding: 20px;
			border-radius: 8px;
			margin-bottom: 20px;
			box-shadow: 0 2px 4px rgba(0,0,0,0.1);
		}
		
		.url-options {
			margin-top: 10px;
			display: flex;
			gap: 20px;
		}
		
		.url-options label {
			display: flex;
			align-items: center;
			gap: 5px;
			cursor: pointer;
		}
		
		.primary-button {
			background: #4d66a8;
			color: white;
			font-weight: bold;
			padding: 10px 20px;
		}
		
		.info-section {
			display: flex;
			gap: 20px;
			margin-bottom: 20px;
		}
		
		.info-section details {
			flex: 1;
			background: white;
			padding: 15px;
			border-radius: 8px;
			box-shadow: 0 2px 4px rgba(0,0,0,0.1);
		}
		
		.info-section summary {
			cursor: pointer;
			font-weight: bold;
			padding: 5px;
		}
		
		.guide-content, .links-content {
			margin-top: 15px;
		}
		
		.links-content {
			display: flex;
			flex-direction: column;
			gap: 10px;
		}
		
		.links-content a {
			color: #4d66a8;
			text-decoration: none;
			padding: 5px;
		}
		
		.links-content a:hover {
			text-decoration: underline;
		}
		
		kbd {
			background: #f0f0f0;
			border: 1px solid #ccc;
			padding: 2px 5px;
			border-radius: 3px;
			font-family: monospace;
			font-size: 0.9em;
		}
		
		.status-indicator {
			font-size: 14px;
			padding: 5px 10px;
			background: rgba(255,255,255,0.2);
			border-radius: 15px;
		}
		
		.quick-access {
			padding: 10px;
			border-bottom: 1px solid #ccc;
		}
		
		.quick-buttons {
			display: flex;
			flex-wrap: wrap;
			gap: 5px;
			margin-top: 10px;
		}
		
		.quick-buttons button {
			font-size: 12px;
			padding: 8px 12px;
		}
		
		.tabs-container {
			border-top: 1px solid #6e6e6e;
		}
		
		.tabs {
			display: flex;
			background: #1a1a2e;
			border-bottom: 1px solid #6e6e6e;
		}
		
		.tab {
			background: transparent;
			color: #aaa;
			border: none;
			padding: 10px 20px;
			cursor: pointer;
			border-radius: 0;
			margin: 0;
		}
		
		.tab:hover {
			background: rgba(255,255,255,0.1);
		}
		
		.tab.active {
			background: #0b0e15;
			color: white;
			border-bottom: 2px solid #4d66a8;
		}
		
		.command-category {
			margin-bottom: 15px;
			padding: 10px;
			background: #f5f5f5;
			border-radius: 5px;
		}
		
		.command-category h4 {
			margin-top: 0;
			color: #4d66a8;
		}
		
		.command-container {
			margin-bottom: 10px;
			padding: 8px;
			background: white;
			border-radius: 3px;
		}
		
		.command-container h5 {
			margin: 0 0 5px 0;
			font-size: 14px;
		}
		
		.input-wrapper {
			display: flex;
			align-items: center;
			gap: 10px;
			margin-top: 5px;
		}
		
		.range-value {
			min-width: 50px;
			font-family: monospace;
		}
		
		.error {
			border-color: #ff4444 !important;
		}
		
		.error-log {
			color: #ff4444;
		}
		
		.success-log {
			color: #44ff44;
		}
		
		.timestamp {
			color: #666;
			font-size: 11px;
			margin-right: 8px;
		}
		
		.log-entry {
			margin: 2px 0;
			padding: 4px;
			border-radius: 3px;
			cursor: pointer;
			word-break: break-word;
		}
		
		.log-entry:hover {
			background: rgba(255,255,255,0.05);
		}
		
		.loudness-display, .sensors-display {
			padding: 15px;
			background: rgba(255,255,255,0.05);
			margin: 10px;
			border-radius: 5px;
		}
		
		.loudness-meter {
			display: flex;
			align-items: center;
			gap: 10px;
			margin: 5px 0;
		}
		
		.loudness-bar {
			flex: 1;
			height: 20px;
			background: rgba(255,255,255,0.1);
			border-radius: 10px;
			overflow: hidden;
		}
		
		.loudness-fill {
			height: 100%;
			background: linear-gradient(to right, #44ff44, #ffff44, #ff4444);
			transition: width 0.2s;
		}
		
		.help-text {
			font-size: 12px;
			color: #666;
			display: block;
			margin-top: 5px;
		}
		
		.custom-post textarea {
			resize: vertical;
			font-size: 13px;
		}
	</style>
</body>
</html>